﻿using System;
using System.Collections.Generic;
using Business.Common.DataDictionary;
using Business.Common.Exception;
using Business.Common.QueryModel;
using Business.Common.Toolkit;
using Business.DataAccess.Repository.Inventory;
using Business.Domain;
using Business.Domain.Inventory;
using Business.Domain.Wms;
using Framework.Core.Collections;
using Framework.Core.Exception;

namespace Business.Component
{
    public class StockManager
    {
        /// <summary>
        /// 并发锁定对象
        /// </summary>
        private static readonly object Locker = new object();

        #region stock

        public static Stock CreateStock(int warehouseId, long billId, int skuId, int packId, int locationId,
                                        int containerId, int qty, string batchNumber)
        {
            var stock = new Stock
                            {
                                BatchNumber = batchNumber,
                                ContainerId = containerId,
                                LocationId = locationId,
                                Qty = qty,
                                SkuId = skuId,
                                PackId = packId,
                                WarehouseId = warehouseId
                            };

            var repository = new StockRepository();
            int insertResult = repository.Create(stock);

            List<BillSn> billSns = SerialNumberManager.GetBillSn(warehouseId, BillType.InboundBill, billId, skuId,
                                                                 packId, batchNumber);

            // create stock sn
            StockSnRepository billSnRepository = new StockSnRepository();
            foreach (var billSn in billSns)
            {
                StockSn stockSn = new StockSn
                                      {
                                          PackId = packId,
                                          SkuId = skuId,
                                          WarehouseId = warehouseId,
                                          Sn = billSn.Sn
                                      };
                stockSn.Id = billSnRepository.Create(stockSn);
                if (stockSn.Id <= 0)
                    BusinessExceptionHelper.ThrowBusinessException("4301");
            }

            if (insertResult > 0)
            {
                stock.StockId = insertResult;
                AppendStockLog(StockLogType.Receiving, BillType.InboundBill, billId, stock, qty, 0);
                return stock;
            }
            else
            {
                BusinessExceptionHelper.ThrowBusinessException("4300");
            }

            return null;
        }

        public static Stock SplitStock(BillType billType, long billId, Stock sourceStock, int splitQty)
        {
            if (sourceStock == null) return null;
            if (splitQty <= 0) return null;
            if (splitQty >= sourceStock.Qty) return null;

            var newStock = (Stock)sourceStock.Clone();
            newStock.Qty = splitQty;

            sourceStock.Qty = sourceStock.Qty - splitQty;

            var repository = new StockRepository();
            int insertResult = repository.Create(newStock);
            bool updateResult = repository.Update(sourceStock);

            if (insertResult > 0 && updateResult)
            {
                newStock.StockId = insertResult;
                AppendStockLog(StockLogType.Splitting, billType, billId, sourceStock, 0, splitQty);
                AppendStockLog(StockLogType.Splitting, billType, billId, newStock, splitQty, 0);
                return newStock;
            }

            return null;
        }

        public static Stock TransferStock(TransferType transferType, long billId, Stock sourceStock, int targetLocationId,
                                          int targetContainerId, int userId)
        {
            Stock targetStock = GetStock(sourceStock.WarehouseId, targetLocationId, targetContainerId, sourceStock.SkuId, sourceStock.PackId,
                                         sourceStock.BatchNumber);

            var repository = new StockRepository();
            StockLogType type = StockLogType.PutawayOut;
            if (transferType == TransferType.Putaway)
                type = StockLogType.PutawayOut;
            if (transferType == TransferType.Transfer)
                type = StockLogType.TransferOut;
            if (transferType == TransferType.Replenishment)
                type = StockLogType.ReplenishOut;

            if (targetStock == null)
            {
                // 目标位置无同批次货物存在,则直接更改存储位置
                AppendStockLog(type, BillType.TransferBill, billId, sourceStock, 0, sourceStock.Qty);

                sourceStock.LocationId = targetLocationId;
                sourceStock.ContainerId = targetContainerId;
                bool updateResult = repository.Update(sourceStock);

                AppendStockLog(type, BillType.TransferBill, billId, sourceStock, sourceStock.Qty, 0);

                if (updateResult)
                    return sourceStock;
            }
            else
            {
                // 目标位置有同批次货物存在,则直接更改数量
                AppendStockLog(type, BillType.TransferBill, billId, sourceStock, 0, sourceStock.Qty);

                targetStock.Qty = sourceStock.Qty + targetStock.Qty;
                bool updateResult = repository.Update(targetStock);

                AppendStockLog(type, BillType.TransferBill, billId, targetStock, sourceStock.Qty, 0);
                if (updateResult)
                    return targetStock;
            }

            return null;
        }

        public static bool DestroyStock(long billId, Stock stock)
        {
            if (stock == null) return false;

            AppendStockLog(StockLogType.Issuing, BillType.OutboundBill, billId, stock, 0, stock.Qty);
            var repository = new StockRepository();
            return repository.Delete(stock.StockId);
        }

        public static void AppendStockLog(StockLogType logType, BillType billType, long billId, Stock stock,
                                          int inboundQty, int outboundQty)
        {
            var log = new StockLog
                          {
                              BeforeQty = stock.Qty,
                              InboundQty = inboundQty,
                              OutboundQty = outboundQty,
                              AfterQty = stock.Qty + inboundQty - outboundQty,
                              BatchNumber = stock.BatchNumber,
                              ContainerId = stock.ContainerId,
                              LinkBillId = billId,
                              LinkBillType = (int)billType,
                              LocationId = stock.LocationId,
                              LogTime = TypeConvertHelper.DatetimeToString(DateTime.Now),
                              LogType = (int)logType,
                              WarehouseId = stock.WarehouseId,
                              SkuId = stock.SkuId,
                              PackId = stock.PackId
                          };

            var repository = new StockLogRepository();
            repository.Create(log);
        }

        public static Stock GetStock(int warehouseId, int locationId, int containerId, int skuId, int packId, string batchNumber)
        {
            var query = new Query();
            query.Criteria.Add(new Criterion("WarehouseId", CriteriaOperator.Equal, warehouseId));
            query.Criteria.Add(new Criterion("LocationId", CriteriaOperator.Equal, locationId));
            query.Criteria.Add(new Criterion("ContainerId", CriteriaOperator.Equal, containerId));
            query.Criteria.Add(new Criterion("SkuId", CriteriaOperator.Equal, skuId));
            query.Criteria.Add(new Criterion("PackId", CriteriaOperator.Equal, packId));
            query.Criteria.Add(new Criterion("BatchNumber", CriteriaOperator.Equal, batchNumber));
            StockRepository repository = new StockRepository();
            return repository.GetByQuery(query);
        }

        public static bool AssignStock(int warehouseId, long planId, int skuId, int packId, string batchNumber, int qty)
        {
            lock (Locker)
            {
                OutboundPlan outboundPlan = BillManager.GetOutboundPlan(planId);
                if (outboundPlan == null)
                {
                    BusinessExceptionHelper.ThrowBusinessException("4400");
                    return false;
                }

                var query = new Query();
                query.Criteria.Add(new Criterion("WarehouseId", CriteriaOperator.Equal, warehouseId));
                query.Criteria.Add(new Criterion("SkuId", CriteriaOperator.Equal, skuId));
                query.Criteria.Add(new Criterion("PackId", CriteriaOperator.Equal, packId));
                query.Criteria.Add(new Criterion("AreaType", CriteriaOperator.Equal, (int)AreaType.Picking));
                if (batchNumber != string.Empty)
                    query.Criteria.Add(new Criterion("BatchNumber", CriteriaOperator.Equal, batchNumber));

                // set stock order by property
                List<BatchProperty> batchProperties = SkuManager.GetBatchProperty(skuId);
                bool hasExpiringDate = false;
                bool hasProductionDate = false;
                foreach (var property in batchProperties)
                {
                    if (property.PropertyCode.ToUpper() == "EXPIRINGDATE")
                        hasExpiringDate = true;
                    if (property.PropertyCode.ToUpper() == "PRODUCTIONDATE")
                        hasProductionDate = true;
                }

                if (hasExpiringDate)
                    query.OrderClauses.Add(new OrderClause("ExpiringDate", OrderClause.OrderClauseCriteria.Ascending));
                else
                    if (hasProductionDate)
                        query.OrderClauses.Add(new OrderClause("ProductionDate", OrderClause.OrderClauseCriteria.Ascending));
                    else
                        query.OrderClauses.Add(new OrderClause("InboundDate", OrderClause.OrderClauseCriteria.Ascending));

                var repository = new StockRepository();
                List<Stock> stocks = CollectionHelper.ToList<Stock>(repository.GetListByQuery(query));
                int pickingQty = qty;
                foreach (var stock in stocks)
                {
                    // int warehouseId, int locationId, int containerId, int skuId, int packId, string batchNumber
                    int lockedQty = LockManager.GetLockedQty(warehouseId, stock.LocationId, stock.ContainerId, skuId, packId, batchNumber);
                    int availableQty = stock.Qty - lockedQty;
                    // 此库存可用数量为0或为负数则跳过此库存条目
                    if (availableQty <= 0) continue;

                    if (availableQty >= pickingQty)
                    {
                        // 此库存可用数量足够分配
                        Lock @lock = new Lock
                        {
                            ContainerId = stock.ContainerId,
                            CreateTime = TypeConvertHelper.DatetimeToString(DateTime.Now),
                            CreateUser = 0,
                            LocationId = stock.LocationId,
                            LockMode = (int)LockMode.Automatic,
                            LockObject = stock.StockId,
                            LockReason = (int)LockReason.Pick,
                            LockTime = TypeConvertHelper.DatetimeToString(DateTime.Now),
                            LockType = (int)LockType.Inventory,
                            Operator = 0,
                            PackId = stock.PackId,
                            Qty = pickingQty,
                            Remark = outboundPlan.PlanNumber,
                            SkuId = stock.SkuId,
                            WarehouseId = warehouseId
                        };
                        LockManager.CreateLock(@lock);

                        pickingQty = 0;
                        break;
                    }
                    else
                    {
                        // 此库存可用数量不足分配
                        Lock @lock = new Lock
                        {
                            ContainerId = stock.ContainerId,
                            CreateTime = TypeConvertHelper.DatetimeToString(DateTime.Now),
                            CreateUser = 0,
                            LocationId = stock.LocationId,
                            LockMode = (int)LockMode.Automatic,
                            LockObject = stock.StockId,
                            LockReason = (int)LockReason.Pick,
                            LockTime = TypeConvertHelper.DatetimeToString(DateTime.Now),
                            LockType = (int)LockType.Inventory,
                            Operator = 0,
                            PackId = stock.PackId,
                            Qty = availableQty,
                            Remark = outboundPlan.PlanNumber,
                            SkuId = stock.SkuId,
                            WarehouseId = warehouseId
                        };
                        LockManager.CreateLock(@lock);

                        pickingQty = pickingQty - availableQty;
                        continue;
                    }
                }

                if (pickingQty > 0)
                {
                    BusinessExceptionHelper.ThrowBusinessException("4302");
                    return false;
                }

                return true;
            }
        }

        #endregion stock

        #region serialnumber stock

        public static StockSn CreateStockSn(int warehouseId, int skuId, int packId, string sn)
        {
            var stockSn = new StockSn
                              {
                                  PackId = packId,
                                  SkuId = skuId,
                                  Sn = sn,
                                  WarehouseId = warehouseId
                              };
            var repository = new StockSnRepository();
            stockSn.Id = repository.Create(stockSn);
            if (stockSn.Id > 0)
                return stockSn;

            return null;
        }

        public static bool DeleteStockSn(int warehouseId, int skuId, int packId, string sn)
        {
            var query = new Query();
            query.Criteria.Add(new Criterion("WarehouseId", CriteriaOperator.Equal, warehouseId));
            query.Criteria.Add(new Criterion("SkuId", CriteriaOperator.Equal, skuId));
            query.Criteria.Add(new Criterion("PackId", CriteriaOperator.Equal, packId));

            var repository = new StockSnRepository();
            StockSn stockSn = repository.GetByQuery(query);
            if (stockSn != null)
                return repository.Delete(stockSn.Id);

            return false;
        }

        #endregion serialnumber stock
    }
}